#include <boost/asio/io_service.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <inttypes.h>
#include <vector>
#include <deque>
#include <queue>
#include <boost/function.hpp>
#include <boost/smart_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/array.hpp>

#pragma once

#define TcpBufferSize 1024
// #define PRINT_MSG
// #define DEBUG_MSG

class WingClient
{

    enum
    {
        ONLINE = 0,
        OFFLINE
    } _state;

public:
    boost::asio::io_service ios_;
    boost::asio::ip::tcp::socket socket_;
    boost::asio::ip::tcp::endpoint endpoint_;
    uint32_t total_recvbytes;

private:
    boost::array<uint8_t, 1024> _buf;
    boost::thread thread_;
    // locks
    boost::mutex port_mutex;
    boost::mutex read_mutex;
    std::queue<std::vector<uint8_t>> send_buffer_;
    std::queue<uint8_t> read_buffer_;
    boost::shared_ptr<boost::asio::deadline_timer> timer_;

public:
    WingClient(std::string ip = "127.0.0.1", uint16_t port = 9002) : socket_(this->ios_),
                                                                     endpoint_(boost::asio::ip::address::from_string(ip), port)
    {
        _state = OFFLINE;
        total_recvbytes = 0;
    }

    bool ready()
    {
        return (_state == ONLINE);
    }

    bool connect()
    {
        boost::system::error_code ec;
        bool ret = false;
        socket_.connect(endpoint_, ec);
        if (ec)
        {
            std::cout << ec.message() << std::endl;
        }
        else
        {
            std::cout << "server connected." << std::endl;
            _state = ONLINE;
            ret = true;
            timer_ = boost::make_shared<boost::asio::deadline_timer>(boost::ref(ios_), boost::posix_time::seconds(10));
        }

        return ret;
    }

    void wait_data()
    {
        if (_state == OFFLINE)
        {
            std::cout << "not connected yet." << std::endl;
            return;
        }

        boost::thread([&]() {
            std::cout << "start waiting data." << std::endl;
            int bytes_to_read = 0;
            while (_state == ONLINE)
            {
                boost::system::error_code ec;
                int bytesTransferred = socket_.receive(boost::asio::buffer(_buf), 0, ec);

                if (ec)
                {
                    std::cout << ec.message() << std::endl;
                    _state = OFFLINE;
                    return;
                }

#if defined(PRINT_MSG)
                std::cout << "I Get: {";
#endif
                boost::mutex::scoped_lock lock(read_mutex);
                for (int i = 0; i < bytesTransferred; i++)
                {
                    if (read_buffer_.size() >= TcpBufferSize)
                        read_buffer_.pop();

                    if (bytesTransferred < _buf.size())
                        read_buffer_.push(_buf[i]);
                    else
                        std::cerr << "[error]: Too much bytes to save" << std::endl;

#if defined(PRINT_MSG)
                    printf("%02X ", _buf[i]);
#endif
                }
#if defined(PRINT_MSG)
                std::cout << "}" << std::endl;
#endif
                total_recvbytes += bytesTransferred;

                if (read_buffer_.size() >= TcpBufferSize)
                    std::cout << "[Warn] buffer is full, old data will be overwitten." << std::endl;

#if defined(PRINT_MSG)
                std::cout << "[INFO] Finish Read" << std::endl;
#endif
            }
            std::cout << "stop waiting data." << std::endl;
        });
    }

    uint16_t read()
    {
        uint16_t ret = 0xFFFF;
        if (!read_buffer_.empty())
        {
            boost::mutex::scoped_lock lock(read_mutex);
            ret = read_buffer_.front();
            read_buffer_.pop();
        }
        return ret;
    }

    uint16_t available(){
        return read_buffer_.size();
    }

    void write(std::vector<uint8_t> &data)
    {
        if (_state == ONLINE)
        {
    #ifdef DEBUG_MSG
            std::cout << "I Send: ";
            std::for_each(data.begin(), data.end(), [](uint8_t b) { printf("%02X ", b); });
            std::cout << std::endl;
    #endif
            boost::mutex::scoped_lock lock(port_mutex);
            try
            {
                socket_.send(boost::asio::buffer(data));
            }
            catch (std::exception &e)
            {
                std::cout << e.what() << std::endl;
            }
        }
    }

    bool is_timeout(){
        return timer_->expires_from_now().is_negative();
    }

    void set_timeout(int t){
        timer_->expires_from_now(boost::posix_time::millisec(t));
    }

};